# $Id: Severity.pm 458 2007-03-20 22:58:38Z jmichael-at-suse-de $
# ------------------------------------------------------------------
#
#    Copyright (C) 2005-2006 Novell/SUSE
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of version 2 of the GNU General Public
#    License published by the Free Software Foundation.
#
# ------------------------------------------------------------------

package Immunix::Severity;
use strict;
use Data::Dumper;

my ($debug) = 0;

sub debug {
    print @_ if $debug;
}

sub new {
    my $self = {};
    $self->{DATABASENAME} = undef;
    $self->{CAPABILITIES} = {};
    $self->{FILES}        = {};
    $self->{REGEXPS}      = {};
    $self->{DEFAULT_RANK} = 10;
    bless($self);
    shift;
    $self->init(@_) if @_;
    return $self;
}

sub init ($;$) {
    my ($self, $resource, $read, $write, $execute, $severity);
    $self = shift;
    $self->{DATABASENAME} = shift;
    $self->{DEFAULT_RANK} = shift if defined $_[0];
    open(DATABASE, $self->{DATABASENAME})
      or die "Could not open severity db $self->{DATABASENAME}: $!\n";
    while (<DATABASE>) {
        chomp();
        next if m/^\s*#/;
        next if m/^\s*$/;

        # leading whitespace is fine; maybe it shouldn't be?
        if (/^\s*\/(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
            my ($path, $read, $write, $execute) = ($1, $2, $3, $4);

            if (index($path, "*") == -1) {

                $self->{FILES}{$path} = {
                    r => $read,
                    w => $write,
                    x => $execute
                };

            } else {

                my $ptr = $self->{REGEXPS};
                my @pieces = split(/\//, $path);

                while (my $piece = shift @pieces) {
                    if (index($piece, "*") != -1) {
                        my $path = join("/", $piece, @pieces);
                        my $regexp = convert_regexp($path);
                        $ptr->{$regexp}{SD_RANK} = {
                            r => $read,
                            w => $write,
                            x => $execute
                        };
                        last;
                    } else {
                        $ptr->{$piece} = {} unless exists $ptr->{$piece};
                        $ptr = $ptr->{$piece};
                    }
                }
            }
        } elsif (m|^\s*CAP|) {
            ($resource, $severity) = split;
            $self->{CAPABILITIES}{$resource} = $severity;
        } else {
            print "unexpected database line: $_\n";
        }
    }
    close(DATABASE);
    debug Dumper($self);
    return $self;
}

#rank:
# handle capability
# handle file
#
# handle capability
#   if the name is in the database, return it
#   otherwise, send a diagnostic message to stderr and return the default
#
# handle file
#   initialize the current return value to 0
#   loop over each entry in the database;
#     find the max() value for each mode that matches and set a 'found' flag
#   if the found flag has not been set, return the default;
#   otherwise, return the maximum from the database

sub handle_capability ($) {
    my ($self, $resource) = @_;

    my $ret = $self->{CAPABILITIES}{$resource};
    if (!defined($ret)) {
        return "unexpected capability rank input: $resource\n";
    }
    return $ret;
}

sub check_subtree {
    my ($tree, $mode, $sev, $first, @rest) = @_;

    # reassemble the remaining path from this directory level
    my $path = join("/", $first, @rest);

    # first check if we have a literal directory match to descend into
    if ($tree->{$first}) {
        $sev = check_subtree($tree->{$first}, $mode, $sev, @rest);
    }

    # if we didn't get a severity already, check for matching globs
    unless ($sev) {

        # check each glob at this directory level
        for my $chunk (grep { index($_, "*") != -1 } keys %{$tree}) {

            # does it match the rest of our path?
            if ($path =~ /^$chunk$/) {

                # if we've got a ranking, check if it's higher than
                # current one, if any
                if ($tree->{$chunk}->{SD_RANK}) {
                    for my $m (split(//, $mode)) {
                        if ((!defined $sev)
                            || $tree->{$chunk}->{SD_RANK}->{$m} > $sev)
                        {
                            $sev = $tree->{$chunk}->{SD_RANK}->{$m};
                        }
                    }
                }
            }
        }
    }

    return $sev;
}

sub handle_file ($$) {
    my ($self, $resource, $mode) = @_;

    # strip off the initial / from the path we're checking
    $resource = substr($resource, 1);

    # break the path into directory-level chunks
    my @pieces = split(/\//, $resource);

    my $sev;

    # if there's a exact match for this path in the db, use that instead of
    # checking the globs
    if ($self->{FILES}{$resource}) {

        # check each piece of the passed mode against the db entry
        for my $m (split(//, $mode)) {
            if ((!defined $sev) || $self->{FILES}{$resource}{$m} > $sev) {
                $sev = $self->{FILES}{$resource}{$m};
            }
        }

    } else {

        # descend into the regexp tree looking for matches
        $sev = check_subtree($self->{REGEXPS}, $mode, $sev, @pieces);

    }

    return (defined $sev) ? $sev : $self->{DEFAULT_RANK};
}

sub rank ($;$) {
    my ($self, $resource, $mode) = @_;

    if (substr($resource, 0, 1) eq "/") {
        return $self->handle_file($resource, $mode);
    } elsif (substr($resource, 0, 3) eq "CAP") {
        return $self->handle_capability($resource);
    } else {
        return "unexpected rank input: $resource\n";
    }
}

sub convert_regexp ($) {
    my ($input) = shift;

    # we need to convert subdomain regexps to perl regexps
    my $regexp = $input;

    # escape + . [ and ] characters
    $regexp =~ s/(\+|\.|\[|\])/\\$1/g;

    # convert ** globs to match anything
    $regexp =~ s/\*\*/.SDPROF_INTERNAL_GLOB/g;

    # convert * globs to match anything at current path level
    $regexp =~ s/\*/[^\/]SDPROF_INTERNAL_GLOB/g;

    # convert {foo,baz} to (foo|baz)
    $regexp =~ y/\{\}\,/\(\)\|/ if $regexp =~ /\{.*\,.*\}/;

    # twiddle the escaped * chars back
    $regexp =~ s/SDPROF_INTERNAL_GLOB/\*/g;
    return $regexp;
}

1;    # so the require or use succeeds
